<?php

    // +----------------------------------------------------------------------
    // | 版本更新
    // +----------------------------------------------------------------------
    // | Copyright (c) 2015-2024 http://www.yicmf.com, All rights reserved.
    // +----------------------------------------------------------------------
    // | Author: 微尘 <yicmf@qq.com>
    // +----------------------------------------------------------------------

    namespace app\event\admin;

    use app\common\event\Base;
	use think\facade\Config;
    use think\facade\Db;
    use think\Exception;
    use think\facade\Log;
    use think\facade\Session;
    use think\facade\Env;
    use app\model\admin\Version as VersionModel;
    use app\event\ucenter\User as UserEvent;
    use app\common\event\Setting as SettingEvent;
    use yicmf\cloud\Download as AppStoreDownload;
    use yicmf\cloud\Server as AppStoreServer;

    class Version extends Base
    {

        /**
         * 原版文件目录
         * @var string
         */
        protected $original_file_path;
        /**
         * 更新包文件目录
         * @var string
         */
        protected $update_file_path;

        protected $db_file_path;

        /**
         * @var AppStoreDownload
         */
        protected $appstoreDownload;

        protected $data;


        /**
         * @var AppStoreServer
         */
        protected $appstore;

        protected $next_version;
        protected $next_build;

        protected $enable = 1;

        protected $backup_path;

        protected function initialize()
        {
            $this->next_version = Session::get('nextVersion.version');
            $this->next_build = Session::get('nextVersion.build');
            $this->original_file_path = Config::get('app.system_update_path') . $this->next_version . DIRECTORY_SEPARATOR . 'original' . DIRECTORY_SEPARATOR;
            $this->update_file_path = Config::get('app.system_update_path') . $this->next_version . DIRECTORY_SEPARATOR . 'package' . DIRECTORY_SEPARATOR;
            $this->backup_path = Config::get('app.system_update_path') . $this->next_version . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR;
            $this->db_file_path = $this->update_file_path . 'update.sql';
            $this->data['type'] = 'message';
        }

        public function finish()
        {
            try {
                $this->data['code'] = 0;
                $this->data['message'] = '成功';
				$nextVersion = Session::get('nextVersion');
                // 设置当前版本号
                $this->_setCurrentVersion($nextVersion);
                // 增加更新日志
                $data['title'] = $nextVersion['title'];
                $data['content'] = $nextVersion['content'];
                $data['url'] = $nextVersion['url'];
                $data['build'] = $nextVersion['build'];
                $data['version'] = $nextVersion['version'];
                $data['admin_id'] = UserEvent::getLogin()['id'];
                Session::delete('nextVersion');
                Session::delete('currentVersion');
                $version = VersionModel::create($data);
                $this->data['code'] = 0;
                $this->data['data'] = $version;
            } catch (Exception $e) {
                $this->data['code'] = 1;
                $this->data['message'] = $e->getMessage();
            }
            return $this->data;
        }

        /**
         * 设置当前版本号
		 * @param $version
		 */
        protected function _setCurrentVersion($version)
        {
            $param = [];
            $param['version'] = $version['version'];
            $param['build'] = $version['build'];
            SettingEvent::update($param, Config::get('cloud.project'));
        }

        /**
         * 升级数据库
         */
        public function upDb()
        {
            try {
                if (!file_exists($this->db_file_path)) {
                    throw new Exception('数据库升级脚本不存在');
                }
                // 执行sql文件
                $this->_executeSqlFile($this->db_file_path, Config::get('database.prefix'));
				$data['code'] = 0;
				$data['message'] = '数据库升级成功';
            } catch (Exception $e) {
                $data['code'] = 1;
				$data['message'] = $e->getMessage();
            }
            return $data;
        }

        public function getDbFilePath()
        {
            if (file_exists($this->db_file_path)) {
                return $this->db_file_path;
            } else {
                return false;
            }
        }

        /**
         * 覆盖代码
         * @return bool
         */
        public function cover()
        {
            $this->data['code'] = 0;
            $this->data['is_end'] = 0;
            try {
                $step = Session::has('update_cover_step') ? Session::pull('update_cover_step') : '_createDirBackup';
                $this->$step();
            } catch (\Exception $e) {
                $this->data['code'] = 1;
                $this->data['message'] = $e->getMessage();
            }
            return $this->data;
        }

        protected function _createDirBackup()
        {
            $this->appstoreDownload = new AppStoreDownload();
            if (!$this->appstoreDownload->createFolder($this->backup_path)) {
                throw new Exception('创建目录失败' . $this->backup_path . '请检查权限。');
            }
            $this->data['message'] = '*创建备份文件夹：<span class="layui-green">' . $this->backup_path . '</span>成功。<br/>';
            $this->data['code'] = 0;
            $this->data['type'] = 'message';
            Session::set('update_cover_step', '_backupFile');
        }

        /**
         * 备份文件.
         */
        protected function _backupFile()
        {
            if (Session::has('need_back_up')) {
                $need_back_up = Session::pull('need_back_up');
            } else {
                $need_back_up = $this->_treeDirectory($this->original_file_path, $this->original_file_path);
                Session::set('need_back_up', $need_back_up);
            }
            $file = array_pop($need_back_up);
            $this->appstoreDownload = new AppStoreDownload();
            // 执行备份
            $from = app()->getRootPath() . ltrim($file, DIRECTORY_SEPARATOR);
            $des = str_replace(realpath(app()->getRootPath()), $this->backup_path, $from);
            $this->appstoreDownload->createFolder(dirname($des));
            if (!is_file($from)) {
                $this->data['message'] = '*备份文件：<span class=layui-red>' . app()->getRootPath() . ltrim($file, DIRECTORY_SEPARATOR) . '</span>不存在，跳过备份。<br/>';
            } elseif (!copy($from, $des)) {
                $this->data['message'] = '*备份文件：<span class=layui-red>' . app()->getRootPath() . ltrim($file, DIRECTORY_SEPARATOR) . '</span>异常，跳过备份。<br/>';
            } else {
                chmod($des, 0777);
                $this->data['message'] = '*备份文件：<span class="layui-green">' . app()->getRootPath() . ltrim($file, DIRECTORY_SEPARATOR) . '</span>成功。<br/>';
            }
            if (count($need_back_up) > 0) {
                Session::set('need_back_up', $need_back_up);
                $this->data['code'] = 0;
                $this->data['type'] = 'message';
                Session::set('update_cover_step', '_backupFile');
            } else {
                $this->data['code'] = 0;
                $this->data['message'] = $this->data['message'] . '*备份文件完成。<br>';
                $this->data['type'] = 'message';
                Session::set('update_cover_step', '_coverFile');
            }
        }


        /**
         * 覆盖文件.
         */
        protected function _coverFile()
        {
            if (Session::has('need_cover')) {
                $need_cover = Session::pull('need_cover');
            } else {
                $need_cover = $this->_treeDirectory($this->update_file_path, $this->update_file_path);
                Session::set('need_cover', $need_cover);
            }
            $file = array_pop($need_cover);
            if (in_array(ltrim($file, DIRECTORY_SEPARATOR), [
                'update.sql',
                'update.php'
            ])) {
                $this->data['message'] = '*脚本文件：<span class="layui-green">' . app()->getRootPath() . ltrim($file, DIRECTORY_SEPARATOR) . '</span>跳过。<br/>';
            } else {
                // 执行覆盖
                $to = app()->getRootPath() . ltrim($file, DIRECTORY_SEPARATOR);
                $from = str_replace(realpath(app()->getRootPath()), $this->update_file_path, $to);
                // 创建目录
                if (!is_dir(dirname($to))) {
                    $this->appstoreDownload = new AppStoreDownload();
                    $this->appstoreDownload->createFolder(dirname($to));
                }
                if (file_exists($to)) {
                    unlink($to);
                }
               if (!copy($from, $to)) {
                    $this->data['message'] = '*覆盖文件：<span class=layui-red>' . app()->getRootPath() . ltrim($file, DIRECTORY_SEPARATOR) . '</span>异常，请手动查阅相关代码是否正常，或者联系技术人员。<br/>';
                    Log::error('[VERSION]' . $this->data['message']);
                } else {
                    chmod($to, 0777);
                    $this->data['message'] = '*覆盖文件：<span class="layui-green">' . app()->getRootPath() . ltrim($file, DIRECTORY_SEPARATOR) . '</span>成功。<br/>';
                }
            }
            if (count($need_cover) > 0) {
                Session::set('need_cover', $need_cover);
                $this->data['code'] = 0;
                $this->data['type'] = 'message';
                Session::set('update_cover_step', '_coverFile');
            } else {
                $this->data['code'] = 1;
                $this->data['message'] = $this->data['message'] . '*覆盖文件完成。<br>';
                $this->data['type'] = 'message';
                $this->data['is_end'] = 1; 
            }
        }

        /**
         * 执行SQL文件.
         * @param string $file 要执行的sql文件路径
         * @param string $database_prefix 前缀优化
         * @param string $db_charset 数据库编码 默认为utf-8
         * @throws Exception
         * @author  : 微尘 <yicmf@qq.com>
         * @datetime: 2019/1/26 13:33
         */
        protected function _executeSqlFile($file, $database_prefix, $db_charset = 'utf-8')
        {
            $error = true;
            if (!is_readable($file)) {
                throw new Exception('SQL文件不可读');
            }
            $fp = fopen($file, 'rb');
            $sql = fread($fp, filesize($file));
            fclose($fp);
            $sql = str_replace("\r", "\n", str_replace('`' . 'q7_', '`' . $database_prefix, $sql));
            foreach (explode(";\n", trim($sql)) as $query) {
                $query = trim($query);
                if ($query) {
                    if (Db::execute($query) === false) {
                        throw new Exception('SQL文件不可读');
                    }
                }
            }
        }

        /**
         * 文件对比
         * @return array
         */
        public function compare()
        {
            $html = '';
            $data['cover'] = 1;
            $original_files = $this->_treeDirectory($this->original_file_path, $this->original_file_path);
            $update_files = $this->_treeDirectory($this->update_file_path, $this->update_file_path);
            $sql_file = [
                'update.sql' => '<div class="text-info"><i class="layui-icon layui-icon-template-1"></i> 数据库引导文件，通过</div>',
                'delete.php' => '<div class="text-info"><i class="layui-icon layui-icon-fonts-code"></i> 清理引导文件，通过</div>',
                'update.php' => '<div class="text-info"><i class="layui-icon ayui-icon-template"></i> 引导脚本，通过</div>'
            ];
            foreach ($update_files as $file) {
                $html .= '<tr><td>';
                $html .= (('.' === dirname($file)) ? '' : (dirname($file)) . DIRECTORY_SEPARATOR) . '<span class="layui-green">' . basename($file) . '</span>';
                $html .= '</td><td>';
                if (array_key_exists($file, $sql_file)) {
                    $html .= $sql_file[$file];
                    $html .= '</td><td></td>';
                } else {
                    $original_file = $this->original_file_path . $file;
                    $local_file = app()->getRootPath() . $file;
                    $update_file = $this->update_file_path . $file;
                    // 文件赋予权限
                    is_file($original_file) && chmod($original_file, 0777);
                    is_file($update_file) && chmod($update_file, 0777);
                    is_file($local_file) && chmod($local_file, 0777);
                    if (!is_file($original_file) && !is_file($local_file)) {
                        // 全新增加，且没有二次开发当前文件
                        $html .= '<div class="ayui-orange"><i class="layui-icon layui-icon-add-1"></i>新版本新增的文件</div>';
                    } elseif (!is_file($original_file) && is_file($local_file)) {
                        // 全新增加，二次开发有当前文件
                        $this->enable = 0; // 有二次开发的不再支持覆盖
                        $html .= '<div class="layui-red"><i class="layui-icon layui-icon-survey"></i> 二次开发修改文件！</div>';
                    } elseif (is_file($original_file) && is_file($local_file) && md5_file($local_file) === md5_file($original_file)) {
                        // 修改文件 本地文件未修改
                        $html .= '<div class="layui-green"><i class="layui-icon layui-icon-add-1"></i>当前文件可以覆盖</div>';
                    } elseif (is_file($original_file) && is_file($local_file) && md5_file($local_file) !== md5_file($original_file)) {
                        // 修改文件 本地文件被修改
                        $data['cover'] = 0; // 有二次开发的不再支持覆盖
                        $html .= '<div class="layui-red"><i class="layui-icon layui-icon-survey"></i> 二次开发修改文件！</div>';
                    } else {
                        $html .= '<div class="ayui-orange"><i class="layui-icon layui-icon-add-1"></i>原版文件被移除，更新将添加</div>';
                    }
                    $html .= '</td><td>';
                    //写入权限
                    if (is_writable($update_file)) {
                        $html .= '<div class="layui-green"><i class="layui-icon layui-icon-ok"></i> 文件写入权限通过</div>';
                    } else {
                        $html .= '<div class="layui-red"><i class="layui-icon layui-icon-close"></i> 文件不具备写入权限</div>';
                        $data['cover'] = 0;
                    }
                    $html .= '</td></tr>';
                }
            }
            $data['html'] = $html;
            return $data;
        }


        /**
         * 比较文件.
         * @return array
         */
        private function diff($path, $root = null, $ext_file = ['update.sql' => ['db', 1], 'update.php' => ['guide', 1]])
        {
            $root = is_null($root) ? app()->getRootPath() : $root;
            $files = $this->_treeDirectory($path, $path);
            $result = [];
            foreach ($files as $v) {
                // $files[] = str_replace($root, '',dirname($file).DIRECTORY_SEPARATOR.'<span class="layui-green">'.basename($file).'</span>');
                $key = ((DIRECTORY_SEPARATOR === dirname($v)) ? '' : dirname($v)) . DIRECTORY_SEPARATOR . '<span class="layui-green">' . basename($v) . '</span>';
                $local_path = realpath($root) . $v;
                $diff_path = $path . $v;
                // 判断是否是执行文件
                if (array_key_exists(ltrim($v, DIRECTORY_SEPARATOR), $ext_file)) {
                    $result[$key] = $ext_file[ltrim($v, DIRECTORY_SEPARATOR)];
                    continue;
                }
                // 文件赋予权限
                is_file($diff_path) && chmod($diff_path, 0777);
                is_file($local_path) && chmod($local_path, 0777);
                if (!is_file($local_path)) {
                    // 本地文件不存在当前文件
                    $result[$key] = [
                        'add',
                        1
                    ];
                } elseif (md5_file($local_path) !== md5_file($diff_path)) {
                    $result[$key] = [
                        'modified',
                        is_writable($local_path)
                    ];
                } else {
                    $result[$key] = [
                        'ok',
                        is_writable($local_path)
                    ];
                }
            }
            return $result;
        }

        /**
         * 下载升级包
         * @param string $step
         * @return mixed
         * @author  : 微尘 <yicmf@qq.com>
         * @datetime: 2019/3/15 12:50
         */
        public function download($step = '')
        {
            $this->appstoreDownload = new AppStoreDownload();
            $this->appstore = new AppStoreServer();
            $this->data['step'] = $step;
            $this->data['version'] = $this->next_version;
            $this->data['code'] = 0;
            $this->data['is_end'] = 0;
            try {
                $step = Session::has('update_get_file_step') ? Session::pull('update_get_file_step') : '_checkLicense';
				$this->data['$step'] = $step;
                $this->$step();
            } catch (Exception $e) {
                $this->data['code'] = 1;
                $this->data['message'] = $e->getMessage();
            }
            return $this->data;
        }

        /**
         * 授权检查并获取下载信息
         */
        protected function _checkLicense()
        {
			$this->data['type'] = 'message';
            $data = $this->appstore->data(['version' => $this->next_version])->action('version.download');
            if (isset($data['code']) && 0 == $data['code']) {
                Session::set('system_update_version', $data['data']);
				Session::set('update_get_file_step', '_createDownloadFile');
                $this->data['message'] = '*授权检查通过。<br/>';
                $this->data['code'] = 0;
            } else {
                $this->data['code'] = 1;
                $this->data['message'] = $data['message'] ;
            }
        }

        protected function _createDownloadFile()
        {
            if (!$this->appstoreDownload->createFolder($this->original_file_path)) {
                throw new Exception('创建目录失败' . $this->original_file_path . '请检查权限。');
            }
            if (!$this->appstoreDownload->createFolder($this->update_file_path)) {
                throw new Exception('创建目录失败' . $this->update_file_path . '请检查权限。');
            }
            $this->data['message'] = '*成功创建下载目录。<br/>';
            $this->data['code'] = 0;
            $this->data['type'] = 'message';
            Session::set('update_get_file_step', '_DownloadFileOld');
        }

        protected function _DownloadFileOld()
        {
            $system_update_version = Session::get('system_update_version');
            // 开始下载旧文件包
            $result = $this->appstoreDownload->storageFile($system_update_version['original']['url'], 'version');
            if (0 !== $result['code']) {
                throw new Exception($result['message']);
            }
            // 解压文件到升级目录
            $result = $this->appstoreDownload->extractFileTo($system_update_version['original']['url'], $this->original_file_path, 'version');
            if (0 != $result['code']) {
                throw new Exception('*' . $result['message']);
            }
            $this->data['message'] = '*已经下载旧文件包。<br/>';
            $this->data['code'] = 0;
            $this->data['type'] = 'message';
            Session::set('update_get_file_step', '_DownloadFilePackage');
        }

        protected function _DownloadFilePackage()
        {
            // 获取升级包下载地址
            $version = Session::get('system_update_version');
            // 开始下载'
            $result = $this->appstoreDownload->storageFile($version['package']['url'], 'version');
            if (0 !== $result['code']) {
                throw new Exception($result['message']);
            }
            // 解压文件到升级目录
            $result = $this->appstoreDownload->extractFileTo($version['package']['url'], $this->update_file_path, 'version');
            if (0 != $result['code']) {
                throw new Exception('*' . $result['message']);
            }
            $this->data['message'] = '*已经下载升级文件包。<br/>';
            $this->data['code'] = 0;
            $this->data['type'] = 'message';
            Session::set('update_get_file_step', '_getTreeDirectory');
        }

        protected function _getTreeDirectory()
        {
            $lists = $this->_treeDirectory($this->update_file_path, $this->update_file_path);
            $file_html = '';
            foreach ($lists as $file) {
                $file_html .= (('.' === dirname($file)) ? '' : (dirname($file)) . DIRECTORY_SEPARATOR) . '<span class="layui-green">' . basename($file) . '</span>' . '<br/>';
            }
            $this->data['file'] = $file_html;
            $this->data['code'] = 0;
            $this->data['type'] = 'file';
            Session::set('update_get_file_step', '_end');
        }

        protected function _end()
        {
            $this->data['code'] = 1;
            $this->data['is_end'] = 1;
            $this->data['type'] = 'message';
            $this->data['message'] = '*升级包下载完毕，请核对文件。<br/>';
            $this->data['button'] = '核对文件';
        }

        /**
         * 列出所有的文件.
         *
         * @param $dir
         * @param $root
         *
         * @return array
         */
        protected function _treeDirectory($dir, $root)
        {
            $files = [];
            $filenames = glob($dir . '*');
            foreach ($filenames as $file) {
                if (is_dir($file)) {
                    $files = array_merge($files, $this->_treeDirectory($file . DIRECTORY_SEPARATOR, $root));
                } else {
                    $files[] = str_replace($root, '', $file);
                    //                 $files[] = str_replace($root, '',dirname($file).DIRECTORY_SEPARATOR.'<span class="layui-green">'.basename($file).'</span>');
                }
            }
            return $files;
        }
    }
